﻿/*
 * Copyright (c) 2009, Thiago M. Sayao <thiago.sayao@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the author nor the names of its contributors may be 
 *    used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Net;
using System.IO;
using System.Text;

namespace DotNetBurner.Core
{
    public class HttpRequestState : IDisposable
    {
        readonly MemoryStream _dataStream = new MemoryStream();

        public void Dispose()
        {
            if (Response != null)
                Response.Close();

            if (Stream != null)
                Stream.Close();  
          
            _dataStream.Close();
            _dataStream.Dispose();
        }

        public void CallAndClose()
        {
            if (Response != null)
                Response.Close();

            if (Stream != null)
                Stream.Close();

            EndGetDataCallback(AsyncResult);
        }

        public MemoryStream DataStream
        {
            get
            {
                return _dataStream;
            }
        }

        public HttpWebRequest Request
        {
            get;
            set;
        }

        public Stream Stream
        {
            get;
            set;
        }

        public HttpWebResponse Response
        {
            get;
            set;
        }

        public IAsyncResult AsyncResult
        {
            get; 
            set;
        }

        public AsyncCallback EndGetDataCallback
        {
            get;
            set;
        }

        public Encoding Encoding
        {
            get;
            set;
        }

        public byte[] Buffer
        {
            get;
            set;
        }

        public Object State
        {
            get; 
            set;
        }

        public bool ChangedSinceLastDownload
        {
            get;
            internal set;
        }
    }

    public static class WebUtility
    {
        private static int _bufferSize = 1448;

        static WebUtility()
        {
            ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErr) => true;
        }

        #region GetData
        //AsyncCallback is used to free the asp.net thread when requesting
        public static IAsyncResult BeginGetDataFromUrl(AsyncCallback cb, object cbState, string url)
        {
            return BeginGetDataFromUrl(cb, cbState, url, 30 * 1000); 
        }

        public static IAsyncResult BeginGetDataFromUrl(AsyncCallback cb, object cbState, string url, int timeout)
        {
            return BeginGetDataFromUrl(cb, cbState, url, timeout, true);
        }

        public static IAsyncResult BeginGetDataFromUrl(AsyncCallback cb, object cbState, string url, int timeout, bool compress)
        {
            return BeginGetDataFromUrl(cb, cbState, url, timeout, compress, DateTime.MinValue);
        }

        public static IAsyncResult BeginGetDataFromUrl(AsyncCallback cb, object cbState, string url, int timeout, bool compress, DateTime lastDownloaded)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = timeout;
            request.Method = "GET";

            if (compress)
            {
                request.Headers.Add ("Accept-Encoding", "gzip,deflate");
                request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            }

            request.IfModifiedSince = lastDownloaded;

            HttpRequestState state = new HttpRequestState { 
                Request = request,
                EndGetDataCallback = cb,
                State = cbState
            };

            //This will wrap the callback, so we can call BeginRead on EndRequest
            IAsyncResult result = request.BeginGetResponse(EndGetResponse, state);
            state.AsyncResult = result;

            return result;
        }

        static void EndGetResponse(IAsyncResult result)
        {
            HttpRequestState state = (HttpRequestState)result.AsyncState;

            try
            {
                HttpWebRequest request = state.Request;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);

                if (response.StatusCode == HttpStatusCode.NotModified)
                {
                    state.ChangedSinceLastDownload = false;
                    state.CallAndClose();
                    return;
                }

                state.ChangedSinceLastDownload = true;

                //Set the result encoding 
                Encoding encoding;
                try
                {
                    encoding = Encoding.GetEncoding(response.CharacterSet);
                }
                catch (ArgumentException)
                {
                    //just guess
                    encoding = Encoding.UTF8;
                }

                state.Encoding = encoding;

                Stream stream = response.GetResponseStream();

                state.Response = response;
                state.Stream = stream;
                state.Buffer = new byte[_bufferSize];

                stream.BeginRead(state.Buffer, 0, _bufferSize, EndRead, state);
            }
            catch(WebException)
            {
                state.CallAndClose();
            }
        }

        static void EndRead(IAsyncResult result)
        {
            HttpRequestState state = (HttpRequestState)result.AsyncState;

            int bytesRead = state.Stream.EndRead(result);

            if (bytesRead > 0)
            {
                state.DataStream.Write(state.Buffer, 0, bytesRead);

                //keep reading
                state.Stream.BeginRead(state.Buffer, 0, _bufferSize, EndRead, state);
            }
            else
            {
                state.CallAndClose();
            }
        }

        public static string EndGetDataFromUrl(IAsyncResult result)
        {
            HttpRequestState state = (HttpRequestState) result.AsyncState;

            MemoryStream dataStream = state.DataStream;
            dataStream.Position = 0;

            byte[] buffer = new byte[dataStream.Length];
            dataStream.Read(buffer, 0, (int)dataStream.Length);

            dataStream.Close();

            return state.Encoding.GetString(buffer);
        }
        #endregion GetData

        #region SendData
        public static void SendPostDataAsync(string url, string formData)
        {
            byte[] contentToSend = Encoding.UTF8.GetBytes(formData);

            WebRequest request = WebRequest.Create(url);
            request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
            request.Method = "POST";
            request.ContentLength = contentToSend.Length;

            AsyncCallback cb = delegate(IAsyncResult result)
            {
                Stream stream = request.EndGetRequestStream(result);

                AsyncCallback writeCb = delegate(IAsyncResult writeResult)
                {
                    stream.EndWrite(writeResult);
                    stream.Close();
                };

                try
                {
                    stream.BeginWrite(contentToSend, 0, contentToSend.Length, writeCb, null);
                }
                catch (IOException)
                {
                    stream.Close();
                    throw;
                }
            };

            request.BeginGetRequestStream(cb, null);
        }

        #endregion
    }
}
